// LocalCache.cs
//	
// Author:
//   [NAME] [EMAIL]
//
// Copyright (c) 2008, [OWNER]
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
//    * Neither the name of the [ORGANIZATION] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//

using System;
using System.IO;
using System.Reflection;
using System.Data;
using System.Data.SQLite;
using System.Text;
using System.Collections.Generic;
using System.Threading;
using OpenMetaverse;
using log4net;
using log4net.Config;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;

namespace OpenFuse.Cache
{
	/// <summary>
	/// SQLite based cache 
	/// </summary>
	public partial class LocalCache 
	{
		const int PathLength = 2048;
		private static readonly ILog _logger = LogManager.GetLogger("Cache");

        public Repositories.AssetRepository Assets;
        public Repositories.NodeRepository Nodes;
        public Repositories.AccountRepository Accounts;
		public Repositories.GridRepository Grids;
		
		const string FixedConnectionString = "Data Source=cache.db;Version=3;Cache Size=3000;";

        private static ISessionFactory _sessionFactory;
        private static ISessionFactory SessionFactory
        {
            get
            {
                if (_sessionFactory == null)
                {
					// if the database does not exist, ask nhibernate to create it
					bool createNewDatabase = false;
					if(!System.IO.File.Exists("cache.db"))
						createNewDatabase = true;
					
                    var configuration = new Configuration().Configure();
					configuration.AddAssembly(typeof(Node).Assembly);
                    new SchemaExport(configuration).Execute(createNewDatabase, createNewDatabase, false);
                    _sessionFactory = configuration.BuildSessionFactory();
                }
                
                return _sessionFactory;
            }
        }

		public void PopulateCacheWithDefaults()
		{
			Grid g = new Grid() {
				Name = "Local machine",
				Nick = "Local",
        		Standalone = true,
				LoginUrl = "http://localhost:9000",
				Platform = "OpenSim",
				RegisterUrl = "",
				WelcomeUrl = "",
				EconomyUrl = ""
			};
			Grids.Save(g);
			
			g = new Grid() {
				Name = "Open Simulator test grid",
				Nick = "OSGrid",
        		Standalone = false,
				LoginUrl = "http://osgrid.org:8002",
				Platform = "OpenSim",
				RegisterUrl = "",
				WelcomeUrl = "http://osgrid.org/loginscreen.php",
				EconomyUrl = "http://osgrid.org/"
			};
			Grids.Save(g);
			
			g = new Grid() {
				Name = "The Second Life(TM) beta grid",
				Nick = "Aditi",
        		Standalone = false,
				LoginUrl = "https://login.aditi.lindenlab.com/cgi-bin/login.cgi",
				Platform = "OpenSim",
				RegisterUrl = "",
				WelcomeUrl = "http://secondlife.com/app/login/beta",
				EconomyUrl = ""
			};
			Grids.Save(g);

			Comms.IAccount a = Accounts.NewAccount();
			a.FirstName = "Snowcrash";
			a.LastName = "Short";
			a.Grid = Grids.GetByNick("Local");
			a.Password = "Trilobit";
			Accounts.Save(a);
		}
			
        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }
        
        public static void ResetCache()
		{
			_logger.Debug("ResetCache");

			string localConnectionString = FixedConnectionString  + "New=True;";
			
			if(System.IO.File.Exists("cache.db"))
				System.IO.File.Delete("cache.db");
			
			using(SQLiteConnection connection = new SQLiteConnection(localConnectionString))
			{
				connection.Open();
				using(SQLiteTransaction t = connection.BeginTransaction())
				{
					// Retrieve a list of resource names contained by the assembly. 
					Assembly assembly = Assembly.GetExecutingAssembly();
					string[] resourceNames = assembly.GetManifestResourceNames();
		
					// find the init cache command set
					string initCacheSql = null;
					foreach(string resourceName in resourceNames)
					{
                        if (resourceName.EndsWith("InitCacheDb.sql"))
                        {
                            using (Stream fooStream = assembly.GetManifestResourceStream(resourceName))
                            using (StreamReader sr = new StreamReader(fooStream))
                                initCacheSql = sr.ReadToEnd();
                            break;
                        }
					}
		
					SQLiteCommand cmd = new SQLiteCommand(initCacheSql, connection);
					cmd.ExecuteNonQuery();
		
					t.Commit();
				}
				connection.Close();
			}
		}


		public LocalCache() 
		{
			_logger.Debug("LocalCache ctor(SlSession)");

            Nodes = new Repositories.NodeRepository();
            Assets = new Repositories.AssetRepository();
            Accounts = new Repositories.AccountRepository();
			Grids = new OpenFuse.Cache.Repositories.GridRepository();
			
			if(GetSchemaVersion() != 1)
				throw new Exception("Database schema of the cache is invalid");
		}

		public void InsertNodes(Backend.SlSession session, Node parentNode, List<Node> nodes)
		{
			_logger.Debug("InsertNodes");
			foreach(Node n in nodes)
			{
				Nodes.Save(n);
				if(n.RowId == 0)
					Console.WriteLine("New row");
				List<string> deniedFolders = new List<string>() {
					"/",
//					"/Animations",
//					"/Body Parts",
					"/Calling Cards",
//					"/Clothing",
//					"/Gestures",
					"/Landmarks",
					"/Lost And Found",
//					"/Notecards",
//					"/Objects",
//					"/Photo Album",
//					"/Scripts",
//					"/Sounds",
//					"/Textures",
					"/Trash",
					"/Backup"
				};
		
				if(n.GetType() == typeof(FolderNode))
				{
					if( !deniedFolders.Contains(n.MangledPath.Replace("/"+session.GridName+"/"+session.FullName, "")) )
					{
						_logger.DebugFormat("Traversing {0}", n.MangledPath);
						session.AddCommand(new Backend.Commands.GetChildrenCommand(session, (FolderNode)n, new Backend.Commands.GetChildrenCommand.Result(InsertNodes)));
					}
				}
				else if(n.GetType() == typeof(FileNode))
				{
					session.AddCommand(new Backend.Commands.DownloadFileCommand(session, (FileNode)n, new Backend.Commands.DownloadFileCommand.Result(InsertAsset)));
				}
			}
		}

		public int GetSchemaVersion()
		{
			_logger.Debug("LocalCache.GetSchemaVersion");
            return 1; // ExecuteCommand("PRAGMA user_version");
		}

		private void InsertAsset(Backend.SlSession session, FileNode file, Asset a)
		{
			if(a != null && a.Size>0) {
				Assets.Save(a);
				
				// SL sometimes reports a Zero AssetID, but the returned asset 'a' will contain the correct
				// asset id.
				if(file.AssetId != a.AssetId)
				{
					file.AssetId = a.AssetId;
					Nodes.Save(file);
				}
			}
		}

		// delete the cache for a specific account
		public void FlushCache(Comms.IAccount account)
		{
			// Cascading takes care of all sub nodes
			// TODO handle deletes of assets
			Node n = Nodes.GetByPath("/"+ account.Grid.Nick +"/"+ account.FirstName +"."+ account.LastName);
			Nodes.Delete(n);
		}
	}
}
